﻿//------------------------------------------------------------------------------
//  此代码版权声明为全文件覆盖，如有原作者特别声明，会在下方手动补充
//  此代码版权（除特别声明外的代码）归作者本人Diego所有
//  源代码使用协议遵循本仓库的开源协议及附加协议
//  Gitee源代码仓库：https://gitee.com/diego2098/ThingsGateway
//  Github源代码仓库：https://github.com/kimdiego2098/ThingsGateway
//  使用文档：https://kimdiego2098.github.io/
//  QQ群：605534569
//------------------------------------------------------------------------------

using System.Reflection;

using ThingsGateway.NewLife.X.Reflection;

namespace ThingsGateway.NewLife.X;

/// <summary>弱引用Action</summary>
/// <remarks>
/// 常见的事件和委托，都包括两部分：对象和方法，当然如果委托到静态方法上，对象是为空的。
/// 如果把事件委托到某个对象的方法上，同时就间接的引用了这个对象，导致其一直无法被回收，从而造成内存泄漏。
/// 弱引用Action，原理就是把委托拆分，然后弱引用对象部分，需要调用委托的时候，再把对象“拉”回来，如果被回收了，就没有必要再调用它的方法了。
///
/// 文档 https://newlifex.com/core/weak_action
/// </remarks>
/// <typeparam name="TArgs"></typeparam>
public class WeakAction<TArgs>
{
    #region 属性

    /// <summary>经过包装的新的委托</summary>
    private readonly Action<TArgs> Handler;

    /// <summary>委托方法</summary>
    private readonly MethodBase Method;

    /// <summary>是否只使用一次，如果只使用一次，执行委托后马上取消注册</summary>
    private readonly Boolean Once;

    /// <summary>目标对象。弱引用，使得调用方对象可以被GC回收</summary>
    private readonly WeakReference? Target;

    /// <summary>取消注册的委托</summary>
    private Action<Action<TArgs>>? UnHandler;

    #endregion 属性

    #region 扩展属性

    /// <summary>是否可用</summary>
    public Boolean IsAlive
    {
        get
        {
            var target = Target;
            if (target == null && Method.IsStatic) return true;

            return target != null && target.IsAlive;
        }
    }

    #endregion 扩展属性

    #region 构造

    /// <summary>实例化</summary>
    /// <param name="target">目标对象</param>
    /// <param name="method">目标方法</param>
    public WeakAction(Object? target, MethodInfo method) : this(target, method, null, false) { }

    /// <summary>实例化</summary>
    /// <param name="target">目标对象</param>
    /// <param name="method">目标方法</param>
    /// <param name="unHandler">取消注册回调</param>
    /// <param name="once">是否一次性事件</param>
    public WeakAction(Object? target, MethodInfo method, Action<Action<TArgs>>? unHandler, Boolean once)
    {
        if (target != null)
        {
            Target = new WeakReference(target);
        }
        else
        {
            if (!method.IsStatic) throw new InvalidOperationException("Illegal event, no specified class instance and not a static method!");
        }

        Method = method;
        Handler = Invoke;
        UnHandler = unHandler;
        Once = once;
    }

    /// <summary>实例化</summary>
    /// <param name="handler">事件处理器</param>
    public WeakAction(Delegate handler) : this(handler.Target, handler.Method, null, false) { }

    /// <summary>使用事件处理器、取消注册回调、是否一次性事件来初始化</summary>
    /// <param name="handler">事件处理器</param>
    /// <param name="unHandler">取消注册回调</param>
    /// <param name="once">是否一次性事件</param>
    public WeakAction(Delegate handler, Action<Action<TArgs>> unHandler, Boolean once) : this(handler.Target, handler.Method, unHandler, once) { }

    #endregion 构造

    #region 方法

    /// <summary>把弱引用事件处理器转换为普通事件处理器</summary>
    /// <param name="handler"></param>
    /// <returns></returns>
    public static implicit operator Action<TArgs>(WeakAction<TArgs> handler) => handler.Handler;

    /// <summary>调用委托</summary>
    /// <param name="e"></param>
    public void Invoke(TArgs e)
    {
        //if (!Target.IsAlive) return;
        // Keep in mind that，不要用上面的写法，因为判断可能通过，但是接着就被GC回收了，如果判断Target，则会增加引用
        Object? target = null;
        if (Target == null)
        {
            if (Method.IsStatic) Reflect.Invoke(null, Method, e);
        }
        else
        {
            target = Target.Target;
            if (target != null)
            {
                // 优先使用委托
                if (Method is MethodInfo mi)
                    mi.As<Action<TArgs>>(target)!.Invoke(e);
                else
                    target.Invoke(Method, e);
            }
        }

        // 调用方已被回收，或者该事件只使用一次，则取消注册
        if ((Target != null && target == null || Once) && UnHandler != null)
        {
            UnHandler(Handler);
            UnHandler = null;
        }
    }

    #endregion 方法

    #region 辅助

    /// <summary>已重载</summary>
    /// <returns></returns>
    public override String? ToString()
    {
        if (Method == null) return base.ToString();

        if (Method.DeclaringType != null)
            return $"{Method.DeclaringType.Name}.{Method.Name}";
        else
            return Method.Name;
    }

    #endregion 辅助
}
